; Data storage area at the end of this code was increased by 1 byte
; over the original "NMI ASM 2.asm" source file used in 90's.
; This extra byte (data+26) is used for storing the information
; regarding Interrupt enable/disable state before NMI ($00 if disabled, $01 if enabled).
; I only recently found out that when a LD A,R or LD A,I is executed,
; the state of IFF2 is copied to the parity flag, where it can be tested or stored.
; Back then I guess I didn't have this information.
	ORG	$386F
;	DISP	$8000			; OPUS specific pseudoinstruction, not needed here
stack	EQU	$3C00			; Stack will be inside BASIC unused empty area
;		************** Header info storage area (17 bytes) ****************
	;	 T  |---------- name (10 chars) ----------| length   start   Par2
header	DEFB	$03,$43,$52,$41,$43,$4B,$45,$52,$20,$AA,$AF,$00,$1B,$00,$40,$FF,$FF
	;	#### This is the start address of the actual NMI routine ($3880) ####
	;	#### At $0066 in the BASIC area we need to modify 3 bytes: $C3 $80 $38 ####
	LD	(data),		BC	; \
	LD	(data+2),	DE	; |
	LD	(data+4),	HL	; |
	LD	(data+6),	SP	; |
	LD	(data+8),	IX	; | Save register contents to ...
	LD	(data+10)	IY	; |
	EXX				; | ... data storage area at the end of this code
	LD	(data+12),	BC	; |
	LD	(data+14),	DE	; |
	LD	(data+16),	HL	; |
	EXX				; /
	LD	SP,	stack		; Set stack inside BASIC unused empty area (with BASIC area R/W in hardware)
	EX	AF,	AF'
	PUSH	AF			; Save AF' contents on stack
	EX	AF	AF'
	PUSH	AF			; Save AF contents on stack
	POP	HL
	LD	(data+18),	HL	; Copy AF contents to (data+18)
	POP	HL
	LD	(data+20),	HL	; Copy AF' contents to (data+20)
	LD	SP,	(data+6)	; Restore initial SP contents (previously saved)
	POP	HL			; When NMI was activated, the current exec addr (PC contents) ...
	LD	(data+22),	HL	; ... was pushed on stack. Now we read it and save it to (data+22)
	LD	SP,	stack-4		; Set stack back to BASIC unused area, below the previously saved contents of AF
; -------------- NEWLY ADDED CODE FOR SAVING THE INTERRUPT ENABLE FLIP-FLOP STATE ------------------
	LD	A,	$00		; \ Assume maskable interrupts were disabled before NMI
	LD	(data+26),	A	; / and save the proper value ($00) in the storage area
; --------------------------------------------------------------------------------------------------
	LD	A,	R
	LD	(data+24),	A	; Save R contents to (data+24)
	LD	A,	I
	LD	(data+25),	A	; Save I contents to (data+25)
; -------------- NEWLY ADDED CODE FOR SAVING THE INTERRUPT ENABLE FLIP-FLOP STATE ------------------
					; When a Load Register A with Register I (LD A, I) instruction
					; or a Load Register A with Register R (LD A, R) instruction
					; is executed, the state of IFF2 is copied to the parity flag
					; where it can be tested or stored. We use this trick on next line:
	CALL	PE	IFF_1		; If maskable interrupts were enabled before NMI,
					; save the proper value in the "data" storage area
; --------------------------------------------------------------------------------------------------
	LD	A,	$00
			; ##### The purpose of next 2 lines is to avoid modifying the IFF2 \
			; ##### interrupt flip-flop's state until returning from this NMI routine so the pre-NMI \
			; ##### interrupt state can be restored correctly when this code ends with RETN.
			; ##### While this NMI routine executes, the maskable interrupts are disabled (IFF1=0) \
			; ##### so the tape saving routine will not be disturbed by interrupts anyway.
	LD	($04D4),	A	; This replaces a "DI" instr. opcode in the SA-FLAG routine with a NOP
	LD	($054F),	A	; This replaces a "EI" instr. opcode in the SA/LD-RET routine with a NOP
LOOP	LD	A,	$EF		; \
	IN	A,	($FE)		; | Test the F1 key (keyboard lines KA12 and K5)
	BIT	5,	A		; /
	CALL	Z,	ONE		; Call "ONE" if F1 pressed (save screen as data block preceded by a standard header)
	LD	A,	$DF		; \
	IN	A,	($FE)		; | Test the F2 key (keyboard lines KA13 and K5)
	BIT	5,	A		; /
	CALL	Z,	TWO		; Call "TWO" if F2 pressed (save registers + 48K user mem)
	LD	A,	$BF		; \
	IN	A,	($FE)		; | Test the F3 key (keyboard lines KA14 and K5)
	BIT	5,	A		; /
	CALL	Z,	THREE		; Call "THREE" if F3 pressed (save registers + 48K user mem minus screen)
	LD	A,	$7F		; \
	IN	A,	($FE)		; | Test the F4 key (keyboard lines KA15 and K5)
	BIT	5,	A		; /
	JR	Z,	FOUR		; Jump to "FOUR" if F4 pressed (return from this NMI routine)
	JR	LOOP
ONE	LD	IX,	header		; Base addr of data to save (header)
	LD	DE,	$0011		; Length of data to save (17 bytes)
	LD	A,	$00		; Flag for saving ($00 for header block)
	CALL	$04C2			; Call the SA-BYTES routine to save the header
	CALL	F1			; Wait for F1 key
	LD	IX,	$4000		; Base addr of data to save (screen)
	LD	DE,	$1B00		; Length of data to save (screen including attrs)
	LD	A,	$FF		; Flag for saving ($FF for data block)
	CALL	$04C2			; Call the SA-BYTES routine to save the screen
	RET
TWO	CALL	FF1			; Save the registers' contents to tape with 00 flag (like a header)
	CALL	F2			; Wait for F2 key
	LD	IX,	$4000		; Base addr of data to save (whole 48K user mem)
	LD	DE,	$C000		; Length of data to save (48K)
	LD	A,	$FF		; Flag for saving ($FF for data block)
	CALL	$04C2			; Call the SA-BYTES routine to save the whole 48K user mem
	RET
THREE	CALL	FF1			; Save the registers' contents to tape with 00 flag (like a header)
	CALL	F3			; Wait for F3 key
	LD	IX,	$5B00		; Base addr of data to save (48K user mem less the screen+attrs data)
	LD	DE,	$A500		; Length of data to save (41.25K)
	LD	A,	$FF		; Flag for saving ($FF for data block)
	CALL	$04C2			; Call the SA-BYTES routine to save
	RET
FOUR	LD	BC,	(data)		; \
	LD	DE,	(data+2)	; |
	LD	HL,	(data+4)	; |
	LD	IX,	(data+8)	; |
	LD	IY,	(data+10)	; |
	EXX				; |
	LD	BC,	(data+12)	; | Restore register contents (except for SP) from ...
	LD	DE,	(data+14)	; | ... data storage area at the end of this code
	LD	HL,	(data+16)	; |
	EXX				; |
	LD	A,	(data+24)	; |
	LD	R,	A		; |
	LD	A,	(data+25)	; |
	LD	I,	A		; /
	LD	A,	$F3		; \ Restore the "DI" instruction opcode
	LD	($04D4),	A	; / within system SA-FLAG routine
	LD	A,	$FB		; \ Restore the "EI" instruction opcode
	LD	($054F),	A	; / within system SA/LD-RET routine
	POP	AF			; Restore pre-NMI AF register contents
	EX	AF,	AF'
	POP	AF			; Restore pre-NMI AF' register contents
	EX	AF,	AF'		; Set pre-NMI AF as current registers
	LD	SP,	(data+6)	; Restore pre-NMI Stack Pointer contents
	RETN				; ********* Return from this NMI routine **********
FF1	LD	IX,	data		; Base addr of data to save (previously saved registers' contents)
	LD	DE,	27		; Length of data to save (27 bytes)
	LD	A,	$00		; Flag for saving ($00 like for header block)
	CALL	$04C2			; Call the SA-BYTES routine to save the registers)
	RET
F1	LD	A,	$EF		; \
	IN	A,	($FE)		; | Test the F1 key (keyboard lines KA12 and K5)
	BIT	5,	A		; /
	RET	Z			; Return if F1 pressed
	JR	F1
F2	LD	A,	$DF		; \
	IN	A,	($FE)		; | Test the F2 key (keyboard lines KA13 and K5)
	BIT	5,	A		; /
	RET	Z			; Return if F2 pressed
	JR	F2
F3	LD	A,	$BF		; \
	IN	A,	($FE)		; | Test the F3 key (keyboard lines KA14 and K5)
	BIT	5,	A		; /
	RET	Z			; Return if F3 pressed
	JR	F3
; -------------- NEWLY ADDED CODE FOR SAVING THE INTERRUPT ENABLE FLIP-FLOP STATE ------------------
IFF_1	LD	A,	$01		; In case interrupts were enabled before NMI \
	LD	(data+26),	A	; save this information in the storage area
	RET
; --------------------------------------------------------------------------------------------------
data	DEFS	27			; Storage space for saving the CPU registers' contents at NMI

; ############## Allocation map for "data" storage space ##################
;
; data+$00	BC
; data+$02	DE
; data+$04	HL
; data+$06	SP
; data+$08	IX
; data+$0A	IY
; data+$0C	BC'
; data+$0E	DE'
; data+$10	HL'
; data+$12	AF
; data+$14	AF'
; data+$16	PC
; data+$18	R
; data+$19	I
; data+$1A	Interrupt enable/disable state before NMI ($00 if disabled, $01 if enabled)

; ############## HOW TO USE THIS CODE - FULL DOCUMENTATION ################
;
;   This program is a customized NMI routine to be placed in the empty unused area
; of the standard ZX Spectrum 16KB Basic interpreter.

;   It is used for "cracking" Spectrum programs (games etc.) that have a "protected"
; non standard tape recording format. "Cracking" here means saving the program code
; after it finished loading, together with the CPU registers at NMI time and the
; CPU interrupt state. Next, this code can be transferred to a CP/M floppy disk as
; a CP/M .COM program that when executed switches to Basic mode and runs the "cracked"
; Spectrum program, picking up where it left when NMI was activated.

;   The usage of this program is as follows:
; Under a Spectrum Basic having this NMI routine, load the program to be cracked from tape.
; Then switch off the write protection for the Basic DRAM area and push the NMI button.
; At this moment, the menu of this NMI routine is activated, which will respond to 4 keys:

;	F1 - saves the screen in Spectrum "Bytes" format, with a standard header

;	F2 - saves a 27 byte headerless block (markbyte $00) containing the data area
;	     at the end of this routine, followed by another headerless block (markbyte $FF)
;	     containing the $4000-$FFFF memory area.

;	F3 - saves a 27 byte headerless block (markbyte $00) containing the data area
;	     at the end of this routine, followed by another headerless block (markbyte $FF)
;	     containing the $5B00-$FFFF memory area.

;	F4 - returns to the Spectrum program interrupted by NMI.

; The 27 byte headerless block contains, in order, the CPU registers' contents:
;	BC  DE  HL  SP  IX  IY  BC' DE' HL' AF  AF' PC  R  I  IFF
; where IFF is the interrupt flip flop (0 if maskable interrupts disabled, 1 if enabled).

; In order to copy a Spectrum program from tape to a CoBra CP/M floppy disk, proceed as follows:
; - Copy the NMI saved (by F2 or F3 keys) program data block from tape to disk using the CP/M
;   utility KID.COM
; - Under CP/M, run the command "ZSID LOADER.COM"
; - At addresses $0103, $0105, $0107, $0109, using the "S" command in ZSID, enter in order:
;	- the loading address (in Basic) for the NMI saved data block ($4000 if saved by F2,
;	  or $5B00 if saved by F3)
;	- the NMI saved data block length ($C000 if saved by F2, or $A500 if saved by F3)
;	- the start address in Basic (the equivalent under Basic of "adr")
;	- the SP register contents which will be "adr", where "adr" will be specified next:
; - Using the "I filename" and "R200" commands in ZSID, load the NMI saved data block from disk
;   and in it look for an empty area (zeros maybe?) of at least $40 bytes. Within this area keep
;   4 bytes free at the beginning (for a temporary stack used by the two PUSH HL instructions below)
;   and then at the very next address (be that "adr") enter the following code:
;	LD	A,	<R>
;	LD	R,	 A
;	LD	A,	<I>
;	LD	I,	 A
;	LD	HL,	<AF'>
;	PUSH	HL
;	LD	HL,	<AF>
;	PUSH	HL
;	POP	AF
;	EX	AF,	AF'
;	POP	AF
;	EX	AF,	AF'
;	LD	BC,	<BC>
;	LD	DE,	<DE>
;	LD	HL,	<HL>
;	LD	SP,	<SP>+2
;	LD	IX,	<IX>
;	LD	IY,	<IY>
;	EXX
;	LD	BC,	<BC'>
;	LD	DE,	<DE'>
;	LD	HL,	<HL'>
;	EXX
;	EI/DI			;if (data+$1A)=$01/$00
;    *	IM	1 		;(or 2 or 3)
;	JP	<PC>
; The EI instruction will be used if (data+$1A)=$01, or the DI instruction if (data+$1A)=$00.
; The instruction flagged by * is only used if required. Since the Z80 Interrupt Mode cannot be
; tested and saved, the proper IM n instruction will have to be determined by trial and error.
; Try without the IM instruction first. If it works, ok, if it doesn't, try IM 1, IM 2, IM 3
; until the program runs correctly